
package com.feib.soeasy.job.importor;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.StringUtils;

import com.feib.soeasy.job.service.ImportService;
import com.feib.soeasy.model.ColumnValidateError;
import com.feib.soeasy.model.CommissionCodeTable;
import com.feib.soeasy.model.FormatDefinition;
import com.feib.soeasy.model.Payment;
import com.feib.soeasy.model.PaymentItem;
import com.feib.soeasy.model.UploadFile;


/**
 * @title (#)BasicValidator.java<br>
 * @description <br>
 * @author Jimmy Liu<br>
 * @version 1.0.0 2010/11/25
 * @copyright Far Eastern International Bank Copyright (c) 2010<br>
 * @2010/11/27 create by Jimmy Liu<br>
 */
public class BasicValidator extends BaseValidatable {
    
    private static Map<String, Integer> CODE_MAPPING = new HashMap<String, Integer>();
    static
    {
        CODE_MAPPING.put("1", new Integer(1));
        CODE_MAPPING.put("2", new Integer(2));
        CODE_MAPPING.put("3", new Integer(3));
        CODE_MAPPING.put("4", new Integer(4));
        CODE_MAPPING.put("5", new Integer(5));
        CODE_MAPPING.put("6", new Integer(6));
        CODE_MAPPING.put("7", new Integer(7));
        CODE_MAPPING.put("8", new Integer(8));
        CODE_MAPPING.put("9", new Integer(9));
        CODE_MAPPING.put("0", new Integer(0));
        
        CODE_MAPPING.put("A", new Integer(1));
        CODE_MAPPING.put("B", new Integer(2));
        CODE_MAPPING.put("C", new Integer(3));
        CODE_MAPPING.put("D", new Integer(4));
        CODE_MAPPING.put("E", new Integer(5));
        CODE_MAPPING.put("F", new Integer(6));
        CODE_MAPPING.put("G", new Integer(7));
        CODE_MAPPING.put("H", new Integer(8));
        CODE_MAPPING.put("I", new Integer(9));
        
        CODE_MAPPING.put("J", new Integer(1));
        CODE_MAPPING.put("K", new Integer(2));
        CODE_MAPPING.put("L", new Integer(3));
        CODE_MAPPING.put("M", new Integer(4));
        CODE_MAPPING.put("N", new Integer(5));
        CODE_MAPPING.put("O", new Integer(6));
        CODE_MAPPING.put("P", new Integer(7));
        CODE_MAPPING.put("Q", new Integer(8));
        CODE_MAPPING.put("R", new Integer(9));
        
        CODE_MAPPING.put("S", new Integer(2));
        CODE_MAPPING.put("T", new Integer(3));
        CODE_MAPPING.put("U", new Integer(4));
        CODE_MAPPING.put("V", new Integer(5));
        CODE_MAPPING.put("W", new Integer(6));
        CODE_MAPPING.put("X", new Integer(7));
        CODE_MAPPING.put("Y", new Integer(8));
        CODE_MAPPING.put("Z", new Integer(9));
        

        CODE_MAPPING.put("*", new Integer(0));
    }

    private Set<String> virtualAccounts = new HashSet<String>();
    
    protected SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
    
    /**
     * @param uploadFile
     * @param formatDefinition
     * @param columnValidateErrors
     */
    public BasicValidator(UploadFile uploadFile, FormatDefinition formatDefinition, List<ColumnValidateError> columnValidateErrors, ImportService importService) {
        super(uploadFile, formatDefinition, columnValidateErrors, importService);
    }

    /* (non-Javadoc)
     * @see com.feib.soeasy.job.BaseValidatable#Validate(com.feib.soeasy.model.Payment)
     */
    @Override
    public boolean validate(Payment payment, long lineNumber) {
        boolean passed = true;
        
        // Rule01: 繳費期限不可小於系統日期
        // 上傳資料時，若繳費期限已超過系統日期，檢核出現錯誤訊息，不允許上傳(jtrac 編號: EBILLING-48)
        String payDueStr = dateFormat.format(payment.getPayDue());
        String nowStr = dateFormat.format(new Date());
        if (payDueStr.compareTo(nowStr) < 0)
        {
            passed = false;
            super.addColumnValidateError(lineNumber, super.getColumnTitle("payDue"), "繳費期限小於系統日期("+payment.getVirtualAccount()+")", null);
        }
        
        // Rule02: 虛擬帳號是否重複
        if (this.hasVirtualAccount(payment.getVirtualAccount(), payment.getPayDue()))
        {
            passed = false;
            super.addColumnValidateError(lineNumber, super.getColumnTitle("virtualAccount"), "虛擬帳號重複("+payment.getVirtualAccount()+")", null);
        }
        
        // Rule03: sum(PaymentItem.amount) =Payment.totalAmount
        long sumOfItemAmount = 0; // 明細金額欄位累加
        List<PaymentItem> paymentItems = payment.getPaymentItems();
        
        // 逐個明細累加
        for (PaymentItem item: paymentItems)
        {
            if (null != item.getAmount())
            {
                sumOfItemAmount += item.getAmount().longValue();
            }
        }
        
        long totalAmount = (null == payment.getTotalAmount() ? 0 : payment.getTotalAmount().longValue());
        if (totalAmount != sumOfItemAmount)
        {
            passed = false;
            super.addColumnValidateError(lineNumber, super.getColumnTitle("totalAmount"), "總金額不符(總金額:"+totalAmount+", 明細加總:"+sumOfItemAmount+")", null);
        }
        
        if (passed)
            passed = doExtraValidate(payment, lineNumber);
        
        // 計算條碼
        if (passed)
        {
            genBarcode(payment);
        }
        
        return passed;
    }
    
    /**
     * 產生條碼<br>
     * 目前只有超商的三段條碼
     * @param payment
     */
    protected void genBarcode(Payment payment) {
        Date payDue = payment.getPayDue();
        String yyyyMMdd = dateFormat.format(payDue);
        String yyyy = yyyyMMdd.substring(0,4);
        String mmdd = yyyyMMdd.substring(4);
        
        String yyyMMdd = String.valueOf(Integer.parseInt(yyyy) - 1911) + mmdd;
        yyyMMdd = StringUtils.leftPad(yyyMMdd, 7, "0");
        
        // 第一段(9)  繳費期限yymmdd (6) + 代收項目XXX(3)
        String barcode1 = yyyMMdd.substring(1) + getCollecionCode(payment.getTotalAmount());
        
        // 第二段(16) 交易序號業者自行訂定(16), 第二段條碼不足位數者，由位數小者補0
        String barcode2 = StringUtils.rightPad(payment.getVirtualAccount(), 16, "0");
        
        // 第三段(15) 應繳日期mmdd (4) + 檢碼(2) + 應繳金額(9)
        String barcode3 = mmdd + "**" + StringUtils.leftPad(payment.getTotalAmount().toString(), 9, "0");
        
        barcode3 = genBardcode3(barcode1, barcode2, barcode3);
        
        payment.setBarcodeStores1(barcode1);
        payment.setBarcodeStores2(barcode2);
        payment.setBarcodeStores3(barcode3);
        
        
        // 第二個金額 barcodes
        if (null != payment.getTotalLoanableAmount() && 0 != payment.getTotalLoanableAmount().longValue())
        {
            barcode1 = yyyMMdd.substring(1) + getCollecionCode(payment.getTotalLoanableAmount());
            
            barcode3 = mmdd + "**" + StringUtils.leftPad(payment.getTotalLoanableAmount().toString(), 9, "0");
            barcode3 = genBardcode3(barcode1, barcode2, barcode3);

            payment.setBarcodeStores1Loanable(barcode1);
            payment.setBarcodeStores2Loanable(barcode2);
            payment.setBarcodeStores3Loanable(barcode3);
        }
        
    }

    
    /**
     * 代收項目代碼
     * @return
     */
    protected String getCollecionCode(Long amount)
    {
        for(CommissionCodeTable commissionCodeTable : this.formatDefinition.getCommissionCodeTables())
        {
            // 遠銀通路 與 郵局代收 跳過不處理
            if ("XXX".equals(commissionCodeTable.getCommissionCode()) || "000".equals(commissionCodeTable.getCommissionCode()) || "ZZZ".equals(commissionCodeTable.getCommissionCode()))
                continue;
            
            long beg = null == commissionCodeTable.getAmountScopeS() ? 0 : commissionCodeTable.getAmountScopeS().longValue();
            long end = null == commissionCodeTable.getAmountScopeE() ? 0 : commissionCodeTable.getAmountScopeE().longValue();
            if (beg <= amount && end >= amount)
                return commissionCodeTable.getCommissionCode();
        }
        
        //throw new RuntimeException("金額 "+ amount+" 無對應超商代收交易代號！");
        // 遠銀 bug 單編號   EBILLING-31: 上傳記錄查詢-檢核未通過原因敘述有誤，系統描述第14行「系統錯誤」，正確應為第14行「金額超過代收級距」。
        // throw new ValidateException("金額 "+ amount+" 超過代收級距！");
        
        // 因為有可能不跟帳單業者簽超商通路，或是超過超商的繳費級距，但此筆繳費單應該還是要可以產生，因為還有銀行通路可以繳費
        // 因此，回傳固定代碼，此代碼超商無法辨識，所以刷條碼會不過，無法在超商繳款，但依就可以使用銀行通路
        // 且報表也不會有問題
        return "XXX";
        
    }

    /**
     * 超商第三段條碼
     * @param barcode1
     * @param barcode2
     * @param barcode3
     * @return
     */
    private String genBardcode3(String barcode1, String barcode2, String barcode3)
    {
        String result = barcode3;
        
        // 檢查碼之第一碼計算, 各段Barcode’基數值’之加總值 除以11 後之’餘數’,(若餘數為0 則放A, 若餘數為10則放B)        
        // 檢查碼之第二碼計算, 各段Barcode'偶數值’之加總值 除以11 後之’餘數’,(若餘數為0 則放X, 若餘數為10則放Y)
        
        // 第一段 積數/偶數 位總和 
        char[] barcode1Chars = barcode1.toCharArray();
        int odd = 0;
        int even = 0;
        for (int i=0; i < barcode1Chars.length; i++)
        {
            if (0 == i % 2)
            {
                // 基數
                odd = odd + CODE_MAPPING.get(String.valueOf(barcode1Chars[i])).intValue();
            }
            else
            {
                // 偶數
                even = even + CODE_MAPPING.get(String.valueOf(barcode1Chars[i])).intValue();
            }            
        }

        // 第二段 積數/偶數 位總和 
        char[] barcode2Chars = barcode2.toCharArray();
        for (int i=0; i < barcode2Chars.length; i++)
        {
            if (0 == i % 2)
            {
                // 基數
                odd = odd + CODE_MAPPING.get(String.valueOf(barcode2Chars[i])).intValue();
            }
            else
            {
                // 偶數
                even = even + CODE_MAPPING.get(String.valueOf(barcode2Chars[i])).intValue();
            }            
        }

        // 第三段 積數/偶數 位總和 
        char[] barcode3Chars = result.toCharArray();
        for (int i=0; i < barcode3Chars.length; i++)
        {
            if (0 == i % 2)
            {
                // 基數
                odd = odd + CODE_MAPPING.get(String.valueOf(barcode3Chars[i])).intValue();
            }
            else
            {
                // 偶數
                even = even + CODE_MAPPING.get(String.valueOf(barcode3Chars[i])).intValue();
            }            
        }
        
        // 取餘數
        odd = odd % 11;        
        even = even % 11;
        
        // 若餘數為0 則放A, 若餘數為10則放B
        String check = (0==odd ? "A" : (10 == odd? "B" : String.valueOf(odd)));
        
        // 若餘數為0 則放X, 若餘數為10則放Y
        check = check + (0==even ? "X" : (10 == even? "Y" : String.valueOf(even)));
        
        // 置換 **
        result = StringUtils.replace(result, "**", check);
        
        return result;
    }
    
    /**
     * 額外的檢查
     * @param payment
     * @param lineNumber
     * @return
     */
    protected boolean doExtraValidate(Payment payment, long lineNumber) {
        return true;
    }

    /**
     * 檢查繳費帳號是否重複
     * @param virtualAccount 虛擬帳號
     * @param payDue 繳費期限
     * @return
     */
    public boolean hasVirtualAccount(String virtualAccount, Date payDue)
    {
        // setep1: 檔案中是否有重複
        boolean reulst = virtualAccounts.contains(virtualAccount);
        if (!reulst)
        {
            virtualAccounts.add(virtualAccount);
            
            // step2 : 檔案中無重複，再檢查資料庫            
            return importService.hasVirtualAccount(virtualAccount, payDue);
        }
        
        return reulst;
    }

}
